﻿using System;
using System.Reflection;
using System.Runtime.Serialization;
using Pfz.Extensions;

namespace Pfz.Serialization.BinaryBuiltIn
{
	/// <summary>
	/// Class responsible for serializing ISerializable objects.
	/// </summary>
	public sealed class SerializableInterfaceSerializer<T>:
		ItemSerializer<T>
	where
		T: ISerializable
	{
		/// <summary>
		/// Gets the singleton instance.
		/// </summary>
		public static readonly SerializableInterfaceSerializer<T> Instance = new SerializableInterfaceSerializer<T>();

		private static readonly ConstructorInfo _constructor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[]{typeof(SerializationInfo), typeof(StreamingContext)}, null);
		static SerializableInterfaceSerializer()
		{
			if (!typeof(T).IsSerializable)
				throw new ArgumentException("Type parameter T (" + typeof(T).FullName + ") is not marked as [Serializable], even if it implements ISerializable.");

			if (_constructor == null)
				throw new ArgumentException("Can't find a deserialization constructor for type " + typeof(T).FullName);
		}

		private SerializableInterfaceSerializer()
		{
		}

		/// <summary>
		/// Serializes the object.
		/// </summary>
		public override void Serialize(ConfigurableSerializerBase serializer, T item)
		{
			var serializationInfo = new SerializationInfo(typeof(T), new FormatterConverter());
			item.GetObjectData(serializationInfo, new StreamingContext(StreamingContextStates.All, serializer.Context));
			if (serializationInfo.FullTypeName != typeof(T).FullName)
				throw new SerializationException("SerializableInterfaceSerializer can't be used to serialize references. You must implement a custom serializer in that case.");

			int count = serializationInfo.MemberCount;
			serializer.Stream.WriteCompressedInt32(count);
			foreach(SerializationEntry entry in serializationInfo)
			{
				serializer.InnerSerialize(entry.Name);
				serializer.InnerSerialize(entry.Value);
			}
		}

		/// <summary>
		/// Deserializes an object.
		/// </summary>
		public override T Deserialize(ConfigurableSerializerBase deserializer)
		{
			var serializationInfo = new SerializationInfo(typeof(T), new FormatterConverter());
			var context = new StreamingContext(StreamingContextStates.All, deserializer.Context);

			int count = deserializer.Stream.ReadCompressedInt32();
			for(int i=0; i<count; i++)
			{
				string name = (string)deserializer.InnerDeserialize();
				object value = deserializer.InnerDeserialize();
				serializationInfo.AddValue(name, value);
			}

			object result = _constructor.Invoke(new object[]{serializationInfo, context});
			return (T)result;
		}
	}
}
